﻿using System.Collections.Generic;
using System.Linq;

namespace HashFoo.MigratorFoo.Console
{
    public class CommandArgs
    {
        const string BuildConfigCommand = "buildConfig";
        const string ConfigNameCommand = "n";
        const string ConfigPathCommand = "c";
        const string MasterConnectionStringCommand = "m";
        const string PretendCommand = "pretend";
        const string TargetCommand = "t";
        const string VerboseCommand = "verbose";
        const string ForceCommand = "force";

        public static string Usage
        {
            get
            {
                return "\n/h Help (this)" +
                       "\n/c[onfig]:[path to migratorFoo config file]\n" +
                       "/t[arget]:generate -- generates scripts based on the last applied db schema\n" +
                       "/t[arget]:regen -- deletes any scripts not already applied, and then runs generate\n" +
                       "/t[arget]:apply[=version] -- applies all scripts not already applied, or to a specific version\n" +
                       "/t[arget]:apply-all -- applies all scripts not already applied, including any legacy scripts in sub dirs\n" +
                       "/t[arget]:undo -- rolls back to the previous version\n" +
                       "/t[arget]:rollback[=version] -- rolls back to the previous version (same as 'undo'), or to a specific version\n" +
                       "/t[arget]:reset -- rolls back to 0, then runs apply\n" +
                       "/t[arget]:remove-pending -- removes any pending migrations\n" +
                       "/t[arget]:list-dbconfigs -- list the known db configs to run migrations against\n" +
                       "/t[arget]:nuke -- drops the entire database and recreates it using apply-all\n" +
                       "/n[ame]:[dbconfig name] -- runs against a specific db config; runs agianst the default (or first config) if not specified\n" +
                       "/m[aster]:[master connection string] -- if nuking/dropping db, provide a full master connection string (not a config name)\n" +
                       "/verbose -- turns on verbose\n" +
                       "/force -- assumes YES to any prompts\n";
            }
        }

        public string BuildConfig { get; private set; }
        public string ConfigName { get; private set; }
        public string ConfigPath { get; set; }
        public string MasterConnectionString { get; private set; }
        public bool Pretend { get; private set; }
        public string Target { get; private set; }
        public bool Verbose { get; private set; }
        public ulong Version { get; private set; }
        public bool Force { get; set; }

        public static CommandArgs Parse(string[] args)
        {
            //Look for and parse args of form /key:value
            var rawCommands = ParseCommandArgs(args, '/', ':');

            //Set up strongly-typed command args
            var commandArgs = new CommandArgs
            {
                //All values except connection string should be ToLower, but they're often null, so call helper
                ConfigPath = ToLower(rawCommands.FirstOrDefault(x => x.Key == ConfigPathCommand).Value),
                Target = ToLower(rawCommands.FirstOrDefault(x => x.Key == TargetCommand).Value),
                Pretend = rawCommands.ContainsKey(PretendCommand),
                ConfigName = ToLower(rawCommands.FirstOrDefault(x => x.Key == ConfigNameCommand).Value),
                MasterConnectionString = rawCommands.FirstOrDefault(x => x.Key == MasterConnectionStringCommand).Value,
                BuildConfig = ToLower(rawCommands.FirstOrDefault(x => x.Key == BuildConfigCommand).Value),
                Verbose = rawCommands.Any(x => x.Key == VerboseCommand),
                Force = rawCommands.Any(x => x.Key == ForceCommand)
            };

            //Parse the target
            if (!string.IsNullOrWhiteSpace(commandArgs.Target))
            {
                var tokens = commandArgs.Target.Split('=');
                if (tokens.Length == 2)
                {
                    ulong v;
                    if (ulong.TryParse(tokens[1], out v))
                    {
                        commandArgs.Version = v;
                        commandArgs.Target = tokens[0];
                    }
                }
            }

            return commandArgs;
        }

        /// <summary>
        /// Parses command line parameters of form /key=value into {key, value}
        /// </summary>
        public static IDictionary<string, string> ParseCommandArgs(
            string[] args,
            char prefix = '/', char equalSymbol = '=', bool commandsToLowerInvariant = true, bool valuesToLowerInvariant = false)
        {
            var commands = args.Where(s => s.StartsWith(prefix.ToString()))
                // on the split, we set the max substrings to 2, so that if the value has the equal symbol, its ignored
                // in other words, split on the first instance of the equal symbol
                .Select(s => s.Substring(1).Split(new[] { equalSymbol }, 2))
                .Where(x => x.Length <= 2 && x.Length > 0)
                .ToDictionary(
                    x => commandsToLowerInvariant ? x[0].ToLowerInvariant() : x[0],
                    x => valuesToLowerInvariant
                             ? (x.Length == 2 ? x[1].ToLowerInvariant() : string.Empty)
                             : (x.Length == 2 ? x[1] : string.Empty));

            return commands;
        }

        static string ToLower(string p)
        {
            return p == null ? null : p.ToLower();
        }

        public struct TargetCommands
        {
            public const string Apply = "apply";
            public const string ApplyAll = "apply-all";
            public const string Generate = "generate";
            public const string ListDbConfigs = "list-dbconfigs";
            public const string Nuke = "nuke";
            public const string Regen = "regen";
            public const string Reset = "reset";
            public const string Rollback = "rollback";
            public const string Undo = "undo";
            public const string RemovePending = "remove-pending";
        }
    }
}